-- DungeonTentM
-- Created by chengb Apr/13/2015
-- 流浪者帐篷

module("DungeonTentM", package.seeall);

-- 流浪者刷新所需回合数
local INTERVAL_ROUND = 5;

-- 最多可刷出的流浪者数量
local MAX_TRAMP_NUM = 10;

-- 最大胜率
local MAX_WIN_RATIO = 100;

-- 内部函数
local whenPlayerMove;
local whenLeaveDungeon;
local whenNextFloor;
local whenCombatRound;

-- 初始化
function init()
    -- 关注回合数变化
    EventMgr.removeAll("DungeonTentM");

    CombatM.registerStartRoundCB("tent_start_round", whenCombatRound);

    -- 关注玩家离开地牢的事件
    EventMgr.register("DungeonTentM", event.LEAVE_DUNGEON, function()
        whenLeaveDungeon();
    end);

    -- 关注进入下一层的事件
    EventMgr.register("DungeonTentM", event.NEXT_FLOOR, function()
        whenNextFloor();
    end);

    -- 关注层数回退（大地之门等技能）的事件
    EventMgr.register("DungeonTentM", event.BACK_FLOOR, function()
        whenNextFloor();
    end);
end

-- 回合数变化
function whenCombatRound()
    -- 在副本中，不处理
    if DungeonInstanceM.isInInstance() then
        return;
    end

    -- 在BOSS层，不处理
    if DungeonBossM.isInBossLayer() then
        return;
    end

    -- 尝试刷出流浪者
    refreshTramp();
end

-- 关注玩家离开地牢的事件
function whenLeaveDungeon()
    -- 清除回合数
    if ME.user.dbase:query("tent_info") then
        ME.user.dbase:delete("tent_info");
    end
end

-- 进入下一层的回调
function whenNextFloor()
    -- 清除回合数
    if ME.user.dbase:query("tent_info") then
        ME.user.dbase:delete("tent_info");
    end
end

-- 判断该副本是否是流浪者帐篷
function isTentInstance(instanceId)
    local instanceInfo = DungeonInstanceM.getInstanceInfo(instanceId);
    if not instanceInfo then
        return false;
    end

    return instanceInfo["type"] == INSTANCE_TYPE_TENT;
end

-- 是否在流浪者帐篷副本中
function isInTentInstance()
    if not DungeonInstanceM.isInInstance() then
        return false;
    end

    local dungeon = DungeonM.getDungeonContainer();
    local instanceId = dungeon.instanceId;

    if not isTentInstance(instanceId) then
        return false;
    end

    return true;
end

-- 获取帐篷刷怪轮次倒计时
function getRefreshCountDown()
    if isRefreshNumLimit() == true then
        -- 刷怪数量已达上限
        return nil;
    end

    local tentRound = CombatM.getRound() - 1;

    return INTERVAL_ROUND - tentRound % INTERVAL_ROUND;
end

-- 获取流浪者帐篷格子
function findTentGrid()
    -- 先取出所有格子
    local grids = DungeonM.getCurrentDungeon() or {};

    for _, grid in pairs(grids) do
        if grid.state ~= GRID_STATE_DISABLE and
           grid.type == GRID_TYPE_INSTANCE and
           isTentInstance(grid.instanceId) then
            return grid;
        end
    end

    -- 没有找到
    return nil;
end

-- 是否存在已失效的格子
function isInvalidGridExists()
    -- 先取出所有格子
    local grids = DungeonM.getCurrentDungeon() or {};

    local count = 0;
    for _, grid in pairs(grids) do
        repeat
            -- 不能是已失效的地板
            if grid.type == GRID_TYPE_ODD_FLOOR and
                grid.state == GRID_STATE_DISABLE then
                break;
            end

            -- 必须是否已开启的空格或已失效的格子
            if (grid.type == GRID_TYPE_EMPTY and grid.state == GRID_STATE_OPEN) or
                grid.state == GRID_STATE_DISABLE then
                -- 不能刷在钥匙或者有奖励的格子上
                if grid.has_key ~= 1 and grid.bonus == nil then
                    count = count + 1;
                end
            end
        until true
    end

    return count > 0;
end

-- 查找已失效的格子（包含空格）
-- 如果存在多个，随机返回一个，否则返回nil
function findInvalidGrid()
    -- 先取出所有格子
    local grids = DungeonM.getCurrentDungeon() or {};

    local gridList = {};
    for _, grid in pairs(grids) do
        repeat
            -- 不能是已失效的地板
            if grid.type == GRID_TYPE_ODD_FLOOR and
               grid.state == GRID_STATE_DISABLE then
                break;
            end

            -- 必须是已开启的空格或已失效的格子
            if grid.state == GRID_STATE_DISABLE or
                (grid.type == GRID_TYPE_EMPTY and grid.state == GRID_STATE_OPEN) then
                -- 不能刷在钥匙或者有奖励的格子上
                if grid.has_key ~= 1 and grid.bonus == nil then
                    table.insert(gridList, grid);
                end
            end
        until true
    end

    local gridCount = #gridList;
    if gridCount == 0 then
        return nil;
    end

    if gridCount == 1 then
        return gridList[1];
    end

    -- 存在多个，随机返回一个
    local randSeed = DungeonM.getRandSeed("tent_find_invalid_grid");
    local index = randSeed % gridCount + 1;

    -- 根据位置排序
    table.sort(gridList, function(grid1, grid2) return grid1:getPos() < grid2:getPos(); end);

    return gridList[index];
end

-- 是否刷出流浪者
function isRefreshTramp()
    local tentGrid = findTentGrid();
    if not tentGrid then
        -- 流浪者帐篷格子不存在
        return false;
    end

    if not tentGrid:isOpened() then
        -- 格子未翻开
        return false;
    end

    -- 判断回合数
    if CombatM.getRound() % INTERVAL_ROUND ~= 0 then
        return false;
    end

    -- 是否存在已失效的格子
    if not isInvalidGridExists() then
        -- 没有已失效的格子
        DungeonLogM.addLog("[DungeonTentM]没有失效的格子，不刷新流浪者。");
        return false;
    end

    -- 判断已刷出的数量
    if true == isRefreshNumLimit() then
        -- 数量已达上限
        trace("DungeonTentM", "流浪者刷新数量已达上限。");
        return false;
    end

    -- 玩家使用了时间停止
    local prop = PropM.combine(ME.user, "stop_action", 1);
    if PropM.apply(prop, 1) > 0 then
        -- 玩家使用了时间停止
        trace("DungeonTentM", "玩家使用了时间停止。");
        return false;
    end

    return true;
end

-- 刷怪数量是否已达上限
function isRefreshNumLimit()
    local curNum = ME.user.dbase:query("tent_info", "tramp_num", 0);
    return curNum >= MAX_TRAMP_NUM;
end

-- 刷出流浪者
function refreshTramp()
    if not isRefreshTramp() then
        return false;
    end

    local msg = string.format("[DungeonTentM]流浪者帐篷回合数更新至：%d", CombatM.getRound());
    DungeonLogM.addLog(msg);

    -- 随机选择一个已失效的格子
    local grid = findInvalidGrid();
    if not grid then
        return false;
    end

    -- 抽取流浪者
    local element = fetchTramp();
    if not element then
        trace("DungeonTentM", "抽取流浪者失败了。");
        return false;
    end

    -- 将格子刷成怪
    local pos = grid:getPos();
    DungeonM.transGridToMonster(pos, element, "tent");

    -- 更新已刷成的数量
    local curNum = ME.user.dbase:query("tent_info", "tramp_num", 0) + 1;
    ME.user.dbase:set("tent_info", "tramp_num", curNum);

    local msg = string.format("[DungeonTenM]格子%d刷出第%d个流浪者(element = %d)", pos, curNum, element);
    DungeonLogM.addLog(msg);

    -- 抛出帐篷刷怪事件
    EventMgr.fire(event.TENT_REFRESH_MONSTER, { ["element"] = element, });

    return true;
end

-- 抽取一个流浪者
function fetchTramp()
    local grid = findTentGrid();
    if not grid then
        -- 流浪者帐篷格子未刷出
        return nil;
    end

    local instanceId = grid.instanceId;
    local instanceInfo = DungeonInstanceM.getInstanceInfo(instanceId);
    if not instanceInfo then
        return nil;
    end

    local dungeonId = instanceInfo["dungeon_id"];
    local elements = DungeonM.getElementsByDungeon(dungeonId);
    if not elements then
        -- 该副本没有配置元素？
        return nil;
    end

    local class = DungeonM.getClassByType(GRID_TYPE_MONSTER, "monster");
    if not elements[class] then
        -- 该副本没有配置怪物？
        return nil;
    end

    local seed = DungeonM.getRandSeed("fetch_tramp");

    -- 根据权重抽取一个怪物
    local ret = fetchElemBySeed(elements[class], seed);

    return ret["id"];
end

-- 获取赌注大小
function getGambleWagerValue()
    local wagerInfo = FormulaM.invoke("GET_GAMBLE_WAGER_INFO");
    return wagerInfo["wager_value"];
end

-- 获取赌注胜率
function getGambleWinRatio()
    local wagerInfo = FormulaM.invoke("GET_GAMBLE_WAGER_INFO");
    return wagerInfo["win_ratio"];
end

-- 获取获胜奖励
-- @param wagerValue 赌注大小
function calcGambleWinBonus(wagerValue)
    local wagerInfo = FormulaM.invoke("GET_GAMBLE_WAGER_INFO");
    return wagerInfo["win_bonus_mul"] * wagerValue;
end

-- 计算赌博结果
function calcGambleResult(winRatio)
    local seed = DungeonM.getRandSeed("gamble");
    local randValue = seed % MAX_WIN_RATIO;
    local winRatio  = winRatio % MAX_WIN_RATIO;

    -- 迷宫小关一定赢
    local dungeonId = DungeonM.getDungeonId();
    if type(dungeonId) == "number" and dungeonId > 0 and
       DungeonAreaM.query(dungeonId, "type") == "child" then
        return true;
    end

    --塔罗牌冈布奥一定赢
    local classId = ME.user.dbase:query("active_pet", 0);
    if classId == 29 then
        return true;
    end

    if randValue <= winRatio then
        return true;
    end

    return false;
end

-- 获取赌博记录
function getGambleRecord()
    local gambleRecord = ME.user.dbase:query("tent_info", "gamble", false);
    if not gambleRecord then
        -- 未赌过
        return nil;
    end

    -- 赌过了，返回结果
    return gambleRecord.result;
end

---------------------------
-- 赌博
-- @param pos         赌桌格子位置
-- @param wagerValue  赌注大小
function doGamble(pos, wagerValue)
    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        trace("DungeonTentM", "格子(%d)不是赌桌。");
        return false;
    end

    local wargers = getGambleWagerValue();
    local index = table.indexOf(wargers, wagerValue);
    if index == -1 then
        trace("DungeonTentM", "赌注%d不存在", wagerValue);
        return false;
    end

    if ME.user:queryAttrib("search_point") < wagerValue then
        -- 探索点不足
        return false;
    end

    local winRatioList = getGambleWinRatio();
    local winRatio = winRatioList[index];
    if type(winRatio) ~= 'number' then
        error("赌注胜率配置错误。");
        return false;
    end

    -- 计算赌博结果
    local result = calcGambleResult(winRatio);

    -- 记录赌博结果
    local tentInfo = ME.user.dbase:query("tent_info", {});
    tentInfo["gamble"] = { ["wager"] = wagerValue, ["result"] = result };
    ME.user.dbase:set("tent_info", tentInfo);

    if result == true then
        -- 赢了
        trace("DungeonTentM", "赌博结果：赢了。");

        -- 计算获胜奖励
        local bonusValue = calcGambleWinBonus(wagerValue);
        local bonus = { 2, "search_point", bonusValue };

        -- 抛出赌博事件
        EventMgr.fire(event.DO_GAMBLE, { ["result"] = result, ["wagerValue"] = wagerValue, ["bonus"] = bonus, });

        BonusM.doBonus(bonus, "gamble");
    else
        -- 输了
        trace("DungeonTentM", "赌博结果：输了。");

        -- 扣除赌注
        if not ME.user:costAttrib("search_point", wagerValue) then
            return false;
        end

        -- 抛出赌博事件
        EventMgr.fire(event.DO_GAMBLE, { ["result"] = result, ["wagerValue"] = wagerValue, });
    end

    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, grid:getPos());
    end

    return true;
end

-- 清除帐篷尝试升级第三只手
function tryLevelUpThirdHand()
    local classId = FormulaM.invoke("THIRD_HAND_CLASS_ID", {});

    if not EquipM.canUpgradeSpecialEquip(classId) then
        trace("DungeonTentM", "当前不能累计清缴帐篷数。");
        return;
    end

    local addExp = FormulaM.invoke("CALC_TENT_CLEAR_BONUS_EQUIP_EXP");

    -- 奖励前等级
    local perLevel = EquipM.getSpecialEquipLevel(classId);

    BonusM.doBonus({9, classId, addExp}, "tent_equip_exp");

    -- 奖励后等级
    local curLevel = EquipM.getSpecialEquipLevel(classId);

    -- 先取出所有格子
    local grids = DungeonM.getCurrentDungeon() or {};
    local pos = 13;

    for _, grid in pairs(grids) do
        if grid.type == GRID_TYPE_INSTANCE and
            isTentInstance(grid.instanceId) then
            pos = grid:getPos();
            break;
        end
    end

    -- 抛出事件
    EventMgr.fire(event.TENT_UPGRADE_THIRD_HAND, {["pos"] = pos, ["levelUp"] = (curLevel ~= perLevel)});
end

